package cn.usbtg.jfinal.plugin.autoroute;

import cn.usbtg.jfinal.plugin.autoroute.annotation.*;
import cn.usbtg.jfinal.plugin.autoroute.converter.SuffixControllerNameConverter;
import cn.usbtg.jfinal.plugin.autoroute.formatter.AllToLowerControllerNameFormatter;
import cn.usbtg.sutil.EmptyUtil;
import cn.usbtg.sutil.filescanner.FileScanner;
import com.jfinal.config.Routes;
import com.jfinal.core.Controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * jfinal支持注解的自动路由
 * <p/>
 * 关于注解：
 * 1、namespace作用范围：globleNamespace > packgeNamespace > classNamespace
 * 2、对于classNamespace：优先使用AutoRouteNamespace和AutoRouteUrl
 * 3、对于classNamespace：未配置AutoRouteNamespace和AutoRouteUrl时，优先使用AutoRoute
 * 4、对于classNamespace：全部未配置时，使用默认
 * 5、若配置了自定义路由，自定义路由优先级最高；
 * 6、当指定了controllerClass可重复注册或AutoRouteRepeat为true时(配置优先级>注解优先级)，同一个类可被多次路由；为false时，
 * 自动忽略后续的配置使用第一次路由配置；
 * <p/>
 * 路由规则：
 * 1、默认控制器固定为/；
 * 2、普通控制器最终路由为：globleNamespace+packgeNamespace+classNamespace；
 * 3、当未指定命名空间时，根据添加文件夹(或包名)为起始位置，使用子文件夹作为命名空间
 * 4、当未指定访问路径时，类名转换为小写去掉action或controller作为访问路径
 * <p/>
 * 手动路由：
 * 1、手动路由需在方法上添加注解ManuallyRoute，并指定url和http（可选，默认为空字符串）、method（可选，默认为全部）；
 * 2、手工路由原理为通过handler将路由转发到真实jfinal路由，所以与session timeout等标记无关；
 * 3、url可指定占位参数，最终会通过正则将占位参数转换为request的attribute；
 * 4、手动路由是方法级别的路由，跟前面的action路由无冲突，action路由规则仍然有效；
 * <p/>
 * 关于tag：
 * 1、tag标记目标为jfinal的路由；（重要）
 * 2、支持多个tag，支持注解和手工添加；
 * 3、支持根据url获取tag和验证url是否包含tag；
 * 4、路由时可使用AutoRoutesAnnotation.create(me)初始化实例，可在interceptor中使用AutoRoutesAnnotation.getInstance()获取实例；
 */
public class AutoRoutesAnnotation {
    private final Logger log = LoggerFactory.getLogger(AutoRoutesAnnotation.class);

    //扫描基础路径
    private String classPath = new File(this.getClass().getResource("/").getPath()).getPath();
    private String jarPath = (new File(this.getClass().getResource("/").getPath()).getParent() + File.separator + "lib" + File.separator).replace("\\", "/");
    private String classPathPre = "/WEB-INF/classes";

    //jfinal路由对象
    private Routes routes;
    private Class defaultController;//默认控制器
    private String globleNamespace = "";
    private Map<String, RoutesPackges> routesPackges = new LinkedHashMap<>();//自动路由的包名
    private List<String> strJars = new ArrayList<>();//需要扫描的jar包
    private Map<String, Class> skipClasses = new HashMap<>();//排除的类

    private Map<String, Route> routesMap = new HashMap<>();//路由容器(避免重复路由出错，先将获取到的class转换到路由map，去重，当重复时以最后一次为准)
    private Map<String, Route> routesMapCust = new HashMap<>(); //自定义路由容器(若设置了自定义路由，将覆盖自动路由)
    private Map<Class, Boolean> isRepeatMap = new HashMap<>(); //类是否重复的容器

    private Map<String, String> excludedMethodMap = new HashMap<>();//无需处理的method
    private Map<String, Set<String>> tagMap = new HashMap<>();//tag
    private Map<String, ManualRoute> manualRouteMap = new HashMap<>();//手动路由容器
    private Map<String, ManualRoute> manualRouteRegMap = new HashMap<>();//手动路由reg容器
    private String paramKeyRegStr = "\\{[^\\{\\}]+\\}";//手工路由参数占位符匹配正则表达式，用于匹配{param_1}
    private String paramKeyReplaceRegStr = "\\\\{([^\\\\{\\\\}]+)\\\\}";//手工路由参数名称提取正则表达式，用于提取param_1
    private String paramValueRegStr = "([^/\\\\-\\\\?=&]+)";//手工路由参数占位符{param1}的reg替换字符串
    private Pattern paramKeyReg = Pattern.compile(paramKeyRegStr);//手工路由匹配占位符的正则表达式编译

    private List<String> clsSuffixs = new ArrayList<>();//控制器类后缀(提供给默认控制器名称转换器产生controllerName)
    private ControllerNameConverter controllerNameConverter = new SuffixControllerNameConverter();//控制器名称转换器(默认为后缀替换转换器)
    private ControllerNameFormatter controllerNameFormatter = new AllToLowerControllerNameFormatter();//控制器名称格式化器(默认为全部转小写格式化器)

    private static AutoRoutesAnnotation instance = new AutoRoutesAnnotation();

    public static AutoRoutesAnnotation create(Routes routes) {
        instance.setRoutes(routes);
        return instance;
    }

    public static AutoRoutesAnnotation getInstance() {
        return instance;
    }

    private AutoRoutesAnnotation() {
        init();
    }

    /**
     * 初始化
     */
    public void init() {
        clsSuffixs.add("action");
        clsSuffixs.add("controller");
        clsSuffixs.add("service");

        //controller中的无参方法不进行处理
        for (Method m : Controller.class.getMethods()) {
            if (m.getParameterTypes().length == 0) {
                excludedMethodMap.put(m.getName(), m.getName());
            }
        }
    }

    /**
     * 添加自动路由包
     *
     * @param packge          包名
     * @param packgeNamespace 该报名下的class所使用的namespace
     */
    public void addPackage(String packge, String packgeNamespace) {
        if (!this.routesPackges.containsKey(packge)) {
            this.routesPackges.put(packge, new RoutesPackges(packge, packgeNamespace));
        }
    }

    /**
     * 添加自动路由包
     *
     * @param packge 包名
     */
    public void addPackage(String packge) {
        this.addPackage(packge, "");
    }

    /**
     * 添加需要扫描的jar包
     *
     * @param jar jar文件名
     */
    public void addJar(String jar) {
        jar = jar.replace(".", "\\.");
        if (!this.strJars.contains(jar)) {
            this.strJars.add(jar);
        }
    }

    /**
     * 添加排除的类名称
     *
     * @param skipClass 类名
     */
    public void addSkip(Class skipClass) {
        this.skipClasses.put(skipClass.getName(), skipClass);
    }

    /**
     * 添加自定义路由
     *
     * @param controllerKey   url
     * @param controllerClass 类名
     * @param manualRouteKey  手工路由url
     */
    public void addRoute(String controllerKey, Class controllerClass, String manualRouteKey) {
        this.routesMapCust.put(controllerKey, new Route(controllerKey, manualRouteKey, controllerClass));
    }

    /**
     * 添加自定义路由
     *
     * @param controllerKey   url
     * @param controllerClass 类名
     */
    public void addRoute(String controllerKey, Class controllerClass) {
        this.addRoute(controllerKey, controllerClass, null);
    }

    /**
     * 添加控制器类后缀
     *
     * @param suffix 后缀
     */
    public void addClsSuffix(String suffix) {
        this.clsSuffixs.add(suffix);
    }

    /**
     * 移除控制器类后缀
     *
     * @param suffix 后缀
     */
    public void removeClsSuffix(String suffix) {
        this.clsSuffixs.remove(suffix);
    }

    /**
     * 设置controller Class是否允许重复路由
     *
     * @param controllerClass 类名
     * @param isRepeat        是否允许重复路由
     */
    public void setControllerRepeat(Class controllerClass, boolean isRepeat) {
        this.isRepeatMap.put(controllerClass, isRepeat);
    }

    /**
     * 设置手工路由
     *
     * @param url         路由url
     * @param nativeUrl   真实url
     * @param httpMethods 请求method
     * @param cache       是否缓存
     */
    public void addManualRoutes(String url, String nativeUrl, HttpRequestMethod[] httpMethods, boolean cache) {
        ManualRoute[] manualRoutes = setManualRoutes(url, nativeUrl, httpMethods, cache);
        for (ManualRoute manualRoute : manualRoutes) {
            log.debug("ManualRoute:" + manualRoute.getRoute());
        }
    }

    /**
     * 设置手工路由
     *
     * @param url         路由url
     * @param nativeUrl   真实url
     * @param httpMethods 请求method
     */
    public void addManualRoutes(String url, String nativeUrl, HttpRequestMethod[] httpMethods) {
        this.addManualRoutes(url, nativeUrl, httpMethods, true);
    }

    /**
     * 设置手工路由
     *
     * @param url        路由url
     * @param nativeUrl  真实url
     * @param httpMethod 请求method
     * @param cache      是否缓存
     */
    public void addManualRoute(String url, String nativeUrl, HttpRequestMethod httpMethod, boolean cache) {
        ManualRoute manualRoute = setManualRoute(url, nativeUrl, httpMethod, cache);
        log.debug("ManualRoute:" + manualRoute.getRoute());
    }

    /**
     * 设置手工路由
     *
     * @param url        路由url
     * @param nativeUrl  真实url
     * @param httpMethod 请求method
     */
    public void addManualRoute(String url, String nativeUrl, HttpRequestMethod httpMethod) {
        this.addManualRoute(url, nativeUrl, httpMethod, true);
    }

    /**
     * 添加标签
     *
     * @param url  请求url
     * @param tags 标签
     */
    public void addTags(String url, String[] tags) {
        if (EmptyUtil.isEmpty(tags)) {
            throw new RuntimeException("tags is must not be null");
        }

        Set<String> tagSet = new HashSet<>();

        Collections.addAll(tagSet, tags);

        this.tagMap.put(url, tagSet);
    }

    /**
     * 添加标签
     *
     * @param url 请求url
     * @param tag 标签
     */
    public void addTag(String url, String tag) {
        this.addTags(url, new String[]{tag});
    }

    /**
     * 获取url所标记的tag
     *
     * @param url 访问路由路径
     */
    public Set<String> getTags(String url) {
        Set<String> tags = this.tagMap.get(url);
        return EmptyUtil.isNotEmpty(tags) ? tags : new HashSet<String>();
    }

    /**
     * 判断url是否标记了tag
     *
     * @param url 访问路由路径
     */
    public boolean containsTags(String url, String[] tags) {
        if (EmptyUtil.isEmpty(tags)) {
            return false;
        }
        Set<String> tagsExists = this.tagMap.get(url);
        if (EmptyUtil.isEmpty(tagsExists)) {
            return false;
        }

        for (String tag : tags) {
            if (tagsExists.contains(tag)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 判断url是否标记了tag
     *
     * @param url 访问路由路径
     */
    public boolean containsTag(String url, String tag) {
        return this.containsTags(url, new String[]{tag});
    }

    /**
     * 注册路由
     */
    public void route() {
        try {
            log.debug("========================================routes=============================================");

            //获取目标jar
            List<File> fileJars = new ArrayList<>();
            for (String jarStr : strJars) {
                fileJars.addAll(FileScanner.findFiles(jarPath, jarStr));
            }

            //从目标文件夹和目标jar中获取合格的类
            for (Map.Entry<String, RoutesPackges> entry : this.routesPackges.entrySet()) {
                String strPackge = entry.getKey();
                RoutesPackges routesPackges = entry.getValue();

                //将包名转换为正则表达式
                String strPackgeReg = strPackge.replace(".", "\\.");
                strPackgeReg += "\\.?.*";

                //查找项目中的controller
                List<Class> classes = FileScanner.findClass(classPath, true, classPathPre, strPackgeReg, ".*", Controller.class, true);

                //查找jar包中controller
                classes.addAll(FileScanner.findClassFromJars(fileJars, strPackgeReg, ".*", Controller.class));

                //根据路由规则，生成路由到路由容器
                for (Class cls : classes) {
                    String packageName = cls.getPackage().getName();
                    String className = cls.getSimpleName();
                    String namespace = packageName.equals(strPackge) ? "/" : "/" + packageName.substring(packageName.indexOf(strPackge) + strPackge.length() + 1).replace(".", "/") + "/";
                    String url = this.getControllerNameConverter().convert(className);
                    url = this.getControllerNameFormatter().format(url);

                    //若为skip class，跳过
                    if (EmptyUtil.isNotEmpty(skipClasses.get(cls.getName()))) {
                        continue;
                    }

                    //class级别手工路由
                    ManuallyRoute manuallyRoute = (ManuallyRoute) cls.getAnnotation(ManuallyRoute.class);
                    String manuallyRouteClassUrl = null;
                    if (EmptyUtil.isNotEmpty(manuallyRoute)) {
                        manuallyRouteClassUrl = manuallyRoute.url();
                    }

                    //若为默认控制器
                    if (EmptyUtil.isNotEmpty(defaultController) && defaultController.equals(cls)) {
                        routesMap.put("/", new Route("/", manuallyRouteClassUrl, cls));
                        continue;
                    }

                    /*
                        根据annotation设置路由
                     */
                    //获取注解
                    AutoRoute autoRoute = (AutoRoute) cls.getAnnotation(AutoRoute.class);
                    AutoRouteNamespace autoRouteNamespace = (AutoRouteNamespace) cls.getAnnotation(AutoRouteNamespace.class);
                    AutoRouteUrl autoRouteUrl = (AutoRouteUrl) cls.getAnnotation(AutoRouteUrl.class);

                    //先解析AutoRoute
                    if (EmptyUtil.isNotEmpty(autoRoute)) {
                        String namespaceAnnotation = autoRoute.namespace();
                        if (EmptyUtil.isNotEmpty(namespaceAnnotation)) {
                            namespace = namespaceAnnotation;
                        }

                        String urlAnnotation = autoRoute.url();
                        if (EmptyUtil.isNotEmpty(urlAnnotation)) {
                            url = urlAnnotation;
                        }
                    }

                    //当指定了AutoRouteNamespace自动覆盖默认值与AutoRoute
                    if (EmptyUtil.isNotEmpty(autoRouteNamespace)) {
                        String namespaceAnnotation = autoRouteNamespace.value();
                        if (EmptyUtil.isNotEmpty(namespaceAnnotation)) {
                            namespace = namespaceAnnotation;
                        }
                    }

                    //当指定了AutoRouteUrl自动覆盖默认值与AutoRoute
                    if (EmptyUtil.isNotEmpty(autoRouteUrl)) {
                        String urlAnnotation = autoRouteUrl.value();
                        if (EmptyUtil.isNotEmpty(urlAnnotation)) {
                            url = urlAnnotation;
                        }
                    }

                    namespace = namespace.endsWith("/") ? namespace : namespace + "/";
                    url = url.startsWith("/") ? url.substring(1) : url;

                    String controllerKey = namespace + url;

                    //若类不允许重复注册，检查是否已被注册，若已注册则跳过
                    boolean isRepeat = false;
                    AutoRouteRepeat autoRouteRepeat = (AutoRouteRepeat) cls.getAnnotation(AutoRouteRepeat.class);
                    if (EmptyUtil.isNotEmpty(autoRouteRepeat)) {
                        isRepeat = autoRouteRepeat.value();
                    }
                    Boolean clsIsRepeat = this.isRepeatMap.get(cls);
                    if (EmptyUtil.isNotEmpty(clsIsRepeat)) {
                        isRepeat = clsIsRepeat;
                    }
                    if (!isRepeat) {
                        Map.Entry<String, Route> existsRoute = getExistsRoute(cls);
                        if (EmptyUtil.isNotEmpty(existsRoute)) {
                            continue;
                        }
                    }

                    controllerKey = this.globleNamespace + routesPackges.getPackgesNamespace() + controllerKey;
                    Route route = new Route(controllerKey, manuallyRouteClassUrl, cls);

                    if (routesMap.containsKey(controllerKey)) {
                        Route routeExists = routesMap.get(controllerKey);
                        throw new RuntimeException("route already exists," + controllerKey + "=>" + route.getRouteClass().getName() + " for " + routeExists.getAutoRoute() + "=>" + routeExists.getRouteClass().getName());
                    }

                    routesMap.put(controllerKey, route);
                }
            }

            //将自定义路由添加到路由缓存
            //若类不允许重复注册，检查是否已被注册，若已注册则以自定义注册为准自动清除原有注册
            for (Map.Entry<String, Route> entry : this.routesMapCust.entrySet()) {
                String controllerKey = entry.getKey();
                Route route = entry.getValue();

                Class controllerClass = route.getRouteClass();

                boolean isRepeat = false;
                AutoRouteRepeat autoRouteRepeat = (AutoRouteRepeat) controllerClass.getAnnotation(AutoRouteRepeat.class);
                if (EmptyUtil.isNotEmpty(autoRouteRepeat)) {
                    isRepeat = autoRouteRepeat.value();
                }
                Boolean clsIsRepeat = this.isRepeatMap.get(controllerClass);
                if (EmptyUtil.isNotEmpty(clsIsRepeat)) {
                    isRepeat = clsIsRepeat;
                }
                if (!isRepeat) {
                    Map.Entry<String, Route> existsRoute = getExistsRoute(controllerClass);
                    if (EmptyUtil.isNotEmpty(existsRoute)) {
                        this.routesMap.remove(controllerKey);
                    }
                }

                if (routesMap.containsKey(controllerKey)) {
                    Route routeExists = routesMap.get(controllerKey);
                    throw new RuntimeException("route already exists," + controllerKey + "=>" + route.getRouteClass().getName() + " for " + routeExists.getAutoRoute() + "=>" + routeExists.getRouteClass().getName());
                }

                routesMap.put(controllerKey, route);
            }

            //按照key排序
            routesMap = sortMapByKey(routesMap);

            //将路由容器中的路由添加到jfinal
            for (Map.Entry<String, Route> entry : routesMap.entrySet()) {
                String controllerKey = entry.getKey();
                Route route = entry.getValue();

                Class controllerClass = route.getRouteClass();

                this.routes.add(controllerKey, controllerClass);

                log.debug(controllerKey + "=>" + controllerClass.getName());

                //method级别处理
                workMethod(route);
            }

            log.debug("===========================================================================================");
        } catch (Throwable e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 按key排序
     *
     * @param resMap 需要排序的map
     * @return 排序后的map
     */
    private Map<String, Route> sortMapByKey(Map<String, Route> resMap) {
        Map<String, Route> sortMap = new TreeMap<>(new Comparator<String>() {
            @Override
            public int compare(String strPre, String strNext) {
                return strPre.compareTo(strNext);
            }
        });
        sortMap.putAll(resMap);
        return sortMap;
    }

    /**
     * 根据controller Class获取存在的路由配置
     *
     * @param controllerClass 类名
     * @return 存在的路由配置
     */
    private Map.Entry<String, Route> getExistsRoute(Class controllerClass) {
        for (Map.Entry<String, Route> entry : this.routesMap.entrySet()) {
            Route route = entry.getValue();
            if (route.getRouteClass().equals(controllerClass)) {
                return entry;
            }
        }
        return null;
    }

    /**
     * method级别的处理
     *
     * @param route 路由对象
     */
    private void workMethod(Route route) {
        String controllerKey = route.getAutoRoute();
        String manualRouteKey = route.getManualRoute();
        Class controllerClass = route.getRouteClass();

        //获取所有public方法
        Method[] methods = controllerClass.getDeclaredMethods();
        for (Method method : methods) {
            //排除的method、带参的method、非public的method不进行处理
            if (excludedMethodMap.containsKey(method.getName()) || method.getParameterTypes().length != 0 || !Modifier.isPublic(method.getModifiers())) {
                continue;
            }

            //加载session timeout标记检查
            initTag(controllerKey, method);

            //加载手工路由标记检查
            initManuallyRoute(controllerKey, method, manualRouteKey);
        }
    }

    /**
     * 初始化不需要验证session timeout的method
     *
     * @param controllerKey controllerKey
     * @param method        method
     */
    private void initTag(String controllerKey, Method method) {
        RouteTag routeTag = method.getAnnotation(RouteTag.class);
        if (EmptyUtil.isNotEmpty(routeTag)) {
            String routeKey = controllerKey.endsWith("/") ? controllerKey : controllerKey + "/" + method.getName();

            String[] tags = routeTag.value();
            Set<String> tagSet = new HashSet<>();
            Collections.addAll(tagSet, tags);

            this.tagMap.put(routeKey, tagSet);
        }
    }

    /**
     * 初始化需要手工路由的method
     *
     * @param controllerKey  controllerKey
     * @param method         method
     * @param manualRouteKey 手动路由key
     */
    private void initManuallyRoute(String controllerKey, Method method, String manualRouteKey) {
        ManuallyRoute manuallyRoute = method.getAnnotation(ManuallyRoute.class);
        if (EmptyUtil.isEmpty(manuallyRoute)) {
            return;
        }

        if (EmptyUtil.isEmpty(manualRouteKey)) {
            manualRouteKey = controllerKey;
        }

        manualRouteKey = manualRouteKey.endsWith("/") ? manualRouteKey : manualRouteKey + "/";

        String url = manuallyRoute.url();
        if ("".equals(url)) {
            url = manualRouteKey.substring(0, manualRouteKey.length() - 1);
        } else {
            url = url.startsWith("/") ? url.substring(1) : url;
            url = manualRouteKey + url;
        }

        HttpRequestMethod[] httpMethods = manuallyRoute.method();
        String nativeUrl = controllerKey + "/" + method.getName();

        ManualRoute[] manualRoutes = setManualRoutes(url, nativeUrl, httpMethods, manuallyRoute.cache());
        for (ManualRoute manualRoute : manualRoutes) {
            log.debug("    " + manualRoute.getRoute());
        }
    }

    /**
     * 设置手工路由
     *
     * @param url         路由url
     * @param nativeUrl   真实url
     * @param httpMethods 请求method
     */
    private ManualRoute[] setManualRoutes(String url, String nativeUrl, HttpRequestMethod[] httpMethods, boolean cache) {
        ManualRoute[] manualRoutes = new ManualRoute[httpMethods.length];
        for (int i = 0, l = httpMethods.length; i < l; i++) {
            HttpRequestMethod httpMethod = httpMethods[i];
            ManualRoute manualRoute = setManualRoute(url, nativeUrl, httpMethod, cache);
            manualRoutes[i] = manualRoute;
        }
        return manualRoutes;
    }

    /**
     * 设置手工路由
     *
     * @param url        路由url
     * @param nativeUrl  真实url
     * @param httpMethod 请求method
     */
    private ManualRoute setManualRoute(String url, String nativeUrl, HttpRequestMethod httpMethod, boolean cache) {
        if (EmptyUtil.isEmpty(url)) {
            throw new RuntimeException("url is must not be null");
        }
        if (EmptyUtil.isEmpty(nativeUrl)) {
            throw new RuntimeException("nativeUrl is must not be null");
        }
        if (EmptyUtil.isEmpty(httpMethod)) {
            throw new RuntimeException("httpMethod is must not be null");
        }

        url = url.startsWith("/") ? url : "/" + url;
        url = httpMethod.toString() + ":" + url;

        ManualRoute manualRoute = new ManualRoute();
        manualRoute.setUrl(url);
        manualRoute.setMethod(httpMethod);
        manualRoute.setNativeUrl(nativeUrl);

        if (paramKeyReg.matcher(url).find()) {
            String regStr = "^" + url.replaceAll(paramKeyRegStr, paramValueRegStr) + "$";

            if (this.manualRouteRegMap.containsKey(regStr)) {
                ManualRoute manualRouteExists = this.manualRouteRegMap.get(regStr);
                throw new RuntimeException("route is exists," + url + "=>" + nativeUrl + " for " + manualRouteExists.getUrl() + "=>" + manualRouteExists.getNativeUrl());
            }

            Map<String, Boolean> existsParamName = new HashMap<>();
            String urlZw = manualRoute.getUrl();
            String urlZwReg = "^" + urlZw.replaceAll(paramKeyRegStr, paramKeyReplaceRegStr) + "$";
            Pattern kp = Pattern.compile(urlZwReg);
            Matcher km = kp.matcher(urlZw);
            if (km.find()) {
                for (int i = 1; i <= km.groupCount(); i++) {
                    String paramName = km.group(i);
                    if (existsParamName.containsKey(paramName)) {
                        throw new RuntimeException("param name repeat," + url + "=>" + nativeUrl + " for " + paramName);
                    }
                    existsParamName.put(paramName, true);
                }
            }

            manualRoute.setPattern(Pattern.compile(regStr));
            this.manualRouteRegMap.put(regStr, manualRoute);
        } else {
            if (this.manualRouteMap.containsKey(url)) {
                ManualRoute manualRouteExists = this.manualRouteMap.get(url);
                throw new RuntimeException("route is exists," + url + "=>" + nativeUrl + " for " + manualRouteExists.getUrl() + "=>" + manualRouteExists.getNativeUrl());
            }

            this.manualRouteMap.put(url, manualRoute);
        }

        manualRoute.setCache(cache);

        return manualRoute;
    }

    public String getClassPath() {
        return classPath;
    }

    public void setClassPath(String classPath) {
        this.classPath = classPath;
    }

    public String getJarPath() {
        return jarPath;
    }

    public void setJarPath(String jarPath) {
        this.jarPath = jarPath;
    }

    public String getClassPathPre() {
        return classPathPre;
    }

    public void setClassPathPre(String classPathPre) {
        this.classPathPre = classPathPre;
    }

    public Routes getRoutes() {
        return routes;
    }

    public void setRoutes(Routes routes) {
        this.routes = routes;
    }

    public Class getDefaultController() {
        return defaultController;
    }

    public void setDefaultController(Class defaultController) {
        this.defaultController = defaultController;
    }

    public String getGlobleNamespace() {
        return globleNamespace;
    }

    public void setGlobleNamespace(String globleNamespace) {
        this.globleNamespace = globleNamespace;
    }

    public Map<String, RoutesPackges> getRoutesPackges() {
        return routesPackges;
    }

    public void setRoutesPackges(Map<String, RoutesPackges> routesPackges) {
        this.routesPackges = routesPackges;
    }

    public List<String> getStrJars() {
        return strJars;
    }

    public void setStrJars(List<String> strJars) {
        this.strJars = strJars;
    }

    public Map<String, Class> getSkipClasses() {
        return skipClasses;
    }

    public void setSkipClasses(Map<String, Class> skipClasses) {
        this.skipClasses = skipClasses;
    }

    public Map<String, Route> getRoutesMap() {
        return routesMap;
    }

    public void setRoutesMap(Map<String, Route> routesMap) {
        this.routesMap = routesMap;
    }

    public Map<String, Route> getRoutesMapCust() {
        return routesMapCust;
    }

    public void setRoutesMapCust(Map<String, Route> routesMapCust) {
        this.routesMapCust = routesMapCust;
    }

    public Map<Class, Boolean> getIsRepeatMap() {
        return isRepeatMap;
    }

    public void setIsRepeatMap(Map<Class, Boolean> isRepeatMap) {
        this.isRepeatMap = isRepeatMap;
    }

    public Map<String, String> getExcludedMethodMap() {
        return excludedMethodMap;
    }

    public void setExcludedMethodMap(Map<String, String> excludedMethodMap) {
        this.excludedMethodMap = excludedMethodMap;
    }

    public Map<String, Set<String>> getTagMap() {
        return tagMap;
    }

    public void setTagMap(Map<String, Set<String>> tagMap) {
        this.tagMap = tagMap;
    }

    public Map<String, ManualRoute> getManualRouteMap() {
        return manualRouteMap;
    }

    public void setManualRouteMap(Map<String, ManualRoute> manualRouteMap) {
        this.manualRouteMap = manualRouteMap;
    }

    public Map<String, ManualRoute> getManualRouteRegMap() {
        return manualRouteRegMap;
    }

    public void setManualRouteRegMap(Map<String, ManualRoute> manualRouteRegMap) {
        this.manualRouteRegMap = manualRouteRegMap;
    }

    public Pattern getParamKeyReg() {
        return paramKeyReg;
    }

    public void setParamKeyReg(Pattern paramKeyReg) {
        this.paramKeyReg = paramKeyReg;
    }

    public String getParamKeyRegStr() {
        return paramKeyRegStr;
    }

    public void setParamKeyRegStr(String paramKeyRegStr) {
        this.paramKeyRegStr = paramKeyRegStr;
    }

    public String getParamKeyReplaceRegStr() {
        return paramKeyReplaceRegStr;
    }

    public void setParamKeyReplaceRegStr(String paramKeyReplaceRegStr) {
        this.paramKeyReplaceRegStr = paramKeyReplaceRegStr;
    }

    public String getParamValueRegStr() {
        return paramValueRegStr;
    }

    public void setParamValueRegStr(String paramValueRegStr) {
        this.paramValueRegStr = paramValueRegStr;
        this.paramKeyReg = Pattern.compile(this.paramValueRegStr);
    }

    public List<String> getClsSuffixs() {
        return clsSuffixs;
    }

    public void setClsSuffixs(List<String> clsSuffixs) {
        this.clsSuffixs = clsSuffixs;
    }

    public ControllerNameConverter getControllerNameConverter() {
        return controllerNameConverter;
    }

    public void setControllerNameConverter(ControllerNameConverter controllerNameConverter) {
        this.controllerNameConverter = controllerNameConverter;
    }

    public ControllerNameFormatter getControllerNameFormatter() {
        return controllerNameFormatter;
    }

    public void setControllerNameFormatter(ControllerNameFormatter controllerNameFormatter) {
        this.controllerNameFormatter = controllerNameFormatter;
    }
}